This project allows your ESP32 to detect your real-time location (City/Area) without needing Wi-Fi or a SIM card. It compares your GPS coordinates against a pre-loaded internal database of Malaysian locations and displays the result directly on your computer screen via the Arduino Serial Monitor.
Perfect for students, makers, or travelers who want to explore satellite navigation + geolocation data handling with ESP32. Let’s go from zero to hero!
Objective
In this project, you’ll learn how to:
- Interface a Neo-6M GPS module with ESP32 using Hardware Serial.
- Parse NMEA data sentences (Lat, Long, Speed, Time) using the TinyGPS++ library.
- Handle GPS “No Fix” states (searching for satellites).
- Understand how UART communication works between microcontrollers and GPS modules.
By the end, you’ll have your own Portable GPS Monitor running on ESP32!
Circuit Connections

| Components | ESP32 Dev Module |
| GPS VCC | 3.3V |
| GPS GND | GND |
| GPS TX | IO16 (RX2) |
| GPS RX | IO17 (TX2) |
Note: GPS modules need to be outdoors to get a signal lock!
Logic Flow
- Satellite Acquisition: The Neo-6M module searches for satellites. The LED on the GPS module will blink once it has a “fix” (connection).
- Data Transmission: The GPS sends NMEA data strings via Serial (TX pin) to the ESP32.
- Parsing: The ESP32 uses the TinyGPS++ library to extract readable data (Latitude, Longitude, Speed) from the complex NMEA strings.
- Display Logic:
- Searching: If no valid data is received (indoors/cold start), it shows “Waiting for GPS…”.
- Locked: Once satellites are found, Serial Monitor shows Lat/Lng and Speed (km/h).
Code Lab
Step 1: Install Library
Before uploading the code, you need to install these libraries in Arduino IDE:
- TinyGPSPlus by Mikal Hart (for parsing GPS data)
(Go to Sketch -> Include Library -> Manage Libraries -> Search and Install)

Step 2: Code
Copy and paste this code into Arduino IDE:
#include <TinyGPS++.h>
#include <HardwareSerial.h>
// Create TinyGPS++ object
TinyGPSPlus gps;
// Use ESP32 Hardware Serial 2
HardwareSerial gpsSerial(2);
// GPS update interval
unsigned long lastUpdate = 0;
const unsigned long UPDATE_INTERVAL = 2000; // Update every 2 seconds
// Location database structure
struct Location {
const char* name;
double lat;
double lon;
double radius; // in km
};
// Malaysian locations database (expand this as needed)
Location locations[] = {
// Major Cities
{"Kuala Lumpur City Centre", 3.1390, 101.6869, 5},
{"Petaling Jaya", 3.1073, 101.6067, 8},
{"Cyberjaya", 2.9213, 101.6559, 6},
{"Putrajaya", 2.9264, 101.6964, 8},
{"Shah Alam", 3.0733, 101.5185, 10},
{"Klang", 3.0333, 101.4500, 8},
{"Subang Jaya", 3.0438, 101.5869, 6},
{"Cheras", 3.0908, 101.7315, 5},
{"Ampang", 3.1478, 101.7621, 5},
{"Kajang", 2.9922, 101.7885, 5},
{"Seremban", 2.7297, 101.9381, 10},
{"Melaka City", 2.1896, 102.2501, 10},
// Penang
{"Georgetown, Penang", 5.4141, 100.3288, 8},
{"Bayan Lepas, Penang", 5.2973, 100.2655, 5},
{"Butterworth", 5.3991, 100.3642, 5},
// Johor
{"Johor Bahru", 1.4927, 103.7414, 12},
{"Iskandar Puteri", 1.4265, 103.6602, 8},
{"Skudai", 1.5309, 103.6571, 5},
// Other Major Cities
{"Ipoh", 4.5975, 101.0901, 10},
{"Kuantan", 3.8077, 103.3260, 10},
{"Kota Kinabalu", 5.9804, 116.0735, 12},
{"Kuching", 1.5535, 110.3593, 12},
{"Alor Setar", 6.1248, 100.3678, 10},
{"Kota Bharu", 6.1333, 102.2333, 10},
{"Kuala Terengganu", 5.3302, 103.1408, 10},
// Universities
{"Universiti Malaya (UM)", 3.1214, 101.6543, 2},
{"Universiti Kebangsaan Malaysia (UKM)", 2.9230, 101.7807, 3},
{"Universiti Putra Malaysia (UPM)", 2.9869, 101.7174, 3},
{"Universiti Teknologi Malaysia (UTM)", 1.5588, 103.6395, 3},
{"Multimedia University Cyberjaya", 2.9276, 101.6517, 2},
// Airports
{"KLIA (KL International Airport)", 2.7456, 101.7099, 5},
{"KLIA2", 2.7392, 101.7100, 3},
{"Subang Airport", 3.1306, 101.5493, 3},
{"Penang International Airport", 5.2971, 100.2769, 3},
// Shopping Malls
{"Mid Valley Megamall", 3.1176, 101.6774, 1},
{"Pavilion KL", 3.1494, 101.7130, 1},
{"Sunway Pyramid", 3.0733, 101.6067, 1},
{"1 Utama", 3.1502, 101.6146, 1},
};
const int numLocations = sizeof(locations) / sizeof(locations[0]);
void setup() {
// Start Serial Monitor
Serial.begin(115200);
// Start GPS Serial (RX, TX)
gpsSerial.begin(9600, SERIAL_8N1, 16, 17);
Serial.println("ESP32 GPS - Offline Location Detection");
Serial.println("=======================================");
Serial.println("Waiting for GPS signal...");
Serial.println("Make sure GPS module has clear view of sky");
Serial.println("---------------------------------------\n");
}
void loop() {
// Read data from GPS module
while (gpsSerial.available() > 0) {
gps.encode(gpsSerial.read());
}
// Display GPS data every UPDATE_INTERVAL
if (millis() - lastUpdate >= UPDATE_INTERVAL) {
lastUpdate = millis();
displayGPSInfo();
}
}
// Calculate distance between two coordinates (Haversine formula)
double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
double dLat = (lat2 - lat1) * DEG_TO_RAD;
double dLon = (lon2 - lon1) * DEG_TO_RAD;
lat1 = lat1 * DEG_TO_RAD;
lat2 = lat2 * DEG_TO_RAD;
double a = sin(dLat/2) * sin(dLat/2) +
sin(dLon/2) * sin(dLon/2) * cos(lat1) * cos(lat2);
double c = 2 * atan2(sqrt(a), sqrt(1-a));
return 6371.0 * c; // Earth radius in km
}
String getRoughLocation(double lat, double lon) {
String nearestLocation = "Unknown location";
double nearestDistance = 999999;
// Find nearest location within radius
for (int i = 0; i < numLocations; i++) {
double distance = calculateDistance(lat, lon, locations[i].lat, locations[i].lon);
// If within radius and closer than previous match
if (distance <= locations[i].radius && distance < nearestDistance) {
nearestDistance = distance;
nearestLocation = String(locations[i].name);
// Add distance info if not at exact center
if (distance > 0.5) {
nearestLocation += " (~" + String(distance, 1) + " km away)";
}
}
}
// If no match found, try to identify by general region
if (nearestDistance == 999999) {
if (lat >= 1.0 && lat <= 7.5 && lon >= 99.0 && lon <= 120.0) {
nearestLocation = "Malaysia (exact location not in database)";
} else {
nearestLocation = "Outside Malaysia or unknown area";
}
}
return nearestLocation;
}
String getKualaLumpurTime() {
if (!gps.time.isValid()) {
return "INVALID";
}
// Convert UTC to Kuala Lumpur time (GMT+8)
int hour = gps.time.hour() + 8;
// Handle day overflow
if (hour >= 24) {
hour -= 24;
}
String timeStr = "";
if (hour < 10) timeStr += "0";
timeStr += String(hour) + ":";
if (gps.time.minute() < 10) timeStr += "0";
timeStr += String(gps.time.minute()) + ":";
if (gps.time.second() < 10) timeStr += "0";
timeStr += String(gps.time.second());
return timeStr;
}
void displayGPSInfo() {
Serial.println("\n╔════════════════════════════════════════╗");
Serial.println("║ GPS LOCATION DATA ║");
Serial.println("╚════════════════════════════════════════╝");
// Check if location is valid
if (gps.location.isValid()) {
Serial.println("\n YOUR LOCATION:");
Serial.print(" ");
Serial.println(getRoughLocation(gps.location.lat(), gps.location.lng()));
Serial.println("\n COORDINATES:");
Serial.print(" Lat: ");
Serial.println(gps.location.lat(), 6);
Serial.print(" Lon: ");
Serial.println(gps.location.lng(), 6);
// Google Maps link
Serial.println("\n Google Maps Link:");
Serial.print(" https://maps.google.com/?q=");
Serial.print(gps.location.lat(), 6);
Serial.print(",");
Serial.println(gps.location.lng(), 6);
} else {
Serial.println("\n Location: SEARCHING FOR SATELLITES...");
}
// Display time in Kuala Lumpur timezone
if (gps.time.isValid()) {
Serial.println("\n TIME (Kuala Lumpur):");
Serial.print(" ");
Serial.println(getKualaLumpurTime());
}
// Display satellite info
Serial.println("\n SATELLITES:");
if (gps.satellites.isValid()) {
Serial.print(" Connected: ");
Serial.println(gps.satellites.value());
} else {
Serial.println(" Searching...");
}
Serial.println("\n========================================");
// Check if no data received
if (gps.charsProcessed() < 10) {
Serial.println(" WARNING: No GPS data! Check wiring!");
}
}
Step 3: Upload and Run
- Connect your ESP32 board to your computer.
- Select the correct board and port in Arduino IDE.
- Upload the code.
- Important: Bring your setup OUTSIDE under the open sky. GPS signals cannot penetrate concrete roofs effectively.
System Check
When working properly:
- Find Satelites: You need to wait at least 5 minutes
- Serial Monitor: You will see the raw coordinates printed in the Serial Monitor.
- If you see “SEARCHING FOR SATELLITES…”, take the device outdoors or place it near a window. GPS signals struggle to penetrate concrete roofs.
- The LED on the GPS module will start blinking when it has locked onto satellites.
- Once locked, the Serial Monitor will update every 2 seconds with your location, coordinates, and time.

Troubleshooting Guide
| Problem | Solution |
| No GPS Data / “Check Wiring” | Check if GPS TX is connected to ESP32 RX (GPIO 16) and GPS RX to ESP32 TX (GPIO 17). TX must go to RX! |
| Stuck on “Acquiring…” | You are likely indoors. GPS needs a direct line of sight to the sky. It can take 1-5 minutes for a “Cold Start”. |
| GPS LED not blinking | The module has not found satellites yet. Move to a more open area. |
| Speed is always 0 | You need to be moving! GPS speed relies on Doppler shift or position change over time. |
Tips: If you want to find any lines in Arduino IDE, click CTRL+F (CMD + F on Mac) and search the line you want to find.
Customizations Options
- Data Logging: Add an SD Card module to save your hiking/driving path to a
.txtfile. - Speed Alert: Add a buzzer (like in the previous project) to beep if you exceed a certain speed (e.g., > 80 km/h).
- Distance Calculator: Use the
TinyGPSPlusdistance functions to calculate the distance to a specific destination coordinate. - Google Maps: Copy the Lat/Lng from the Serial Monitor and paste it into Google Maps to see your exact location!






